#include "symbolsourcebase.h"

#include <algorithm>

bool NameIndex::findByPrefix(
    const std::vector<NameIndex> &byName, const std::string &prefix,
    const std::function<bool(const NameIndex &)> &cbFound, bool caseSensitive) {
  struct PrefixCmp {
    PrefixCmp(size_t n) : n(n) {}

    bool operator()(const NameIndex &a, const NameIndex &b) {
      return cmp(a, b, false) < 0;
    }

    int cmp(const NameIndex &a, const NameIndex &b, bool caseSensitive) {
      return (caseSensitive ? strncmp : _strnicmp)(a.name, b.name, n);
    }

   private:
    size_t n;
  } prefixCmp(prefix.size());

  if (byName.empty()) return false;

  NameIndex find;
  find.name = prefix.c_str();
  find.index = -1;
  auto found = binary_find(byName.begin(), byName.end(), find, prefixCmp);
  if (found == byName.end()) return false;

  bool result = false;
  for (; found != byName.end() && prefixCmp.cmp(find, *found, false) == 0;
       ++found) {
    if (!caseSensitive || prefixCmp.cmp(find, *found, true) == 0) {
      result = true;
      if (!cbFound(*found)) break;
    }
  }

  return result;
}

bool NameIndex::findByName(const std::vector<NameIndex> &byName,
                           const std::string &name, NameIndex &foundIndex,
                           bool caseSensitive) {
  NameIndex find;
  find.name = name.c_str();
  auto found = binary_find(byName.begin(), byName.end(), find);
  if (found != byName.end()) {
    do {
      if (find.cmp(*found, find, caseSensitive) == 0) {
        foundIndex = *found;
        return true;
      }
      ++found;
    } while (found != byName.end() && find.cmp(find, *found, false) == 0);
  }
  return false;
}

bool SymbolSourceBase::mapSourceFilePdbToDisk(const std::string &pdb,
                                              const std::string &disk) {
  std::string pdblower = pdb, disklower = disk;
  std::transform(pdblower.begin(), pdblower.end(), pdblower.begin(), tolower);
  std::transform(disklower.begin(), disklower.end(), disklower.begin(),
                 tolower);

  // Abort if the disk file doesn't exist
  if (!FileExists(disklower.c_str())) return false;

  // Remove existing mapping if found
  auto found = _sourceFileMapPdbToDisk.find(pdblower);
  if (found != _sourceFileMapPdbToDisk.end()) {
    // Abort if there is already an existing mapping for the destination (we
    // want 1 to 1 mapping) Done here because we can avoid corrupting the state
    if (disklower != found->second && _sourceFileMapDiskToPdb.count(disklower))
      return false;

    // Remove existing mapping
    _sourceFileMapDiskToPdb.erase(found->second);
    _sourceFileMapPdbToDisk.erase(found);
  }
  // Abort if there is already an existing mapping for the destination (we want
  // 1 to 1 mapping)
  else if (_sourceFileMapDiskToPdb.count(disklower)) {
    return false;
  }

  // Insert destinations
  _sourceFileMapPdbToDisk.insert({pdblower, disk});
  _sourceFileMapDiskToPdb.insert({disklower, pdb});
  return true;
}

bool SymbolSourceBase::getSourceFileDiskToPdb(const std::string &disk,
                                              std::string &pdb) const {
  std::string disklower = disk;
  std::transform(disklower.begin(), disklower.end(), disklower.begin(),
                 tolower);
  auto found = _sourceFileMapDiskToPdb.find(disklower);
  if (found == _sourceFileMapDiskToPdb.end()) return false;
  pdb = found->second;
  return true;
}

bool SymbolSourceBase::getSourceFilePdbToDisk(const std::string &pdb,
                                              std::string &disk) const {
  std::string pdblower = pdb;
  std::transform(pdblower.begin(), pdblower.end(), pdblower.begin(), tolower);
  auto found = _sourceFileMapPdbToDisk.find(pdblower);
  if (found == _sourceFileMapPdbToDisk.end()) return false;
  disk = found->second;
  return true;
}
